﻿#region License Information
// Copyright (c) 2005 - 2008, Allan Spartacus Mangune (allan@owlpal.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list 
//   of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of 
//   conditions and the following disclaimer in the documentation and/or other materials provided 
//   with the distribution.
//
// * Neither the name of Owlpal nor the names of its contributors may be used to endorse or 
//   promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
// FITNESS FOR AllowHtmlInComment PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
#endregion

using System;
using System.Xml;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Web.Security;
using System.Web.Hosting;
using System.Web.Management;
using System.Security.Permissions;
using System.Web;
using System.Linq;
using Owlpal.Security;

namespace Owlpal.Models
{
    public class XmlMembershipProvider : MembershipProvider
    {
        #region Members
        private Dictionary<string, MembershipUser> users;
        private string xmlFileName;
        #endregion
        
        #region Properties
        public override bool EnablePasswordRetrieval
        {
            get { return false; }
        }

        public override bool EnablePasswordReset
        {
            get { return false; }
        }

        #region Not Supported
        public override string ApplicationName
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotSupportedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotSupportedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotSupportedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotSupportedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotSupportedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotSupportedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotSupportedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { throw new NotSupportedException(); }
        }
        #endregion

        #endregion

        #region Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null) 
                throw new ArgumentNullException("config");

            if (String.IsNullOrEmpty(name)) name = "XmlMembershipProvider";

            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description",
                    "XML membership provider");
            }

            base.Initialize(name, config);

            string path = config["xmlFileName"];

            if (String.IsNullOrEmpty(path))
                path = "~/App_Data/Users.xml";

            if (!VirtualPathUtility.IsAppRelative(path))
                throw new ArgumentException
                    ("xmlFileName must be app-relative");

            string fullyQualifiedPath = VirtualPathUtility.Combine
                (VirtualPathUtility.AppendTrailingSlash
                (HttpRuntime.AppDomainAppVirtualPath), path);

            xmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath);
            config.Remove("xmlFileName");

            FileIOPermission permission =
                new FileIOPermission(FileIOPermissionAccess.Read,
                xmlFileName);
            permission.Demand();

            if (config.Count > 0)
            {
                string attr = config.GetKey(0);
                if (!String.IsNullOrEmpty(attr))
                    throw new ProviderException
                        ("Unrecognized attribute: " + attr);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public override bool ValidateUser(string username, string password)
        {
            bool isValid = false;
            if (!String.IsNullOrEmpty(username) ||
                !String.IsNullOrEmpty(password))
            {
                try
                {
                    XElement root = XElement.Load(this.xmlFileName);
                    var user = (from u in root.Descendants("User")
                                let UserName = (string)u.Element("UserName")
                                let PasswordHash = (string)u.Element("PasswordHash")
                                let PasswordSalt = (string)u.Element("PasswordSalt")
                                where UserName == username
                                orderby UserName
                                select new
                                {
                                    UserName,
                                    PasswordHash,
                                    PasswordSalt
                                }).FirstOrDefault();
                    isValid = new SaltedHash(user.PasswordSalt, user.PasswordHash).IsValid(password);
                }
                catch (Exception ex)
                {
                    // Error handling code.
                }
            }
            return isValid;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="userIsOnline"></param>
        /// <returns></returns>
        public override MembershipUser GetUser(string username,
            bool userIsOnline)
        {
            if (String.IsNullOrEmpty(username))
            {
                return null;
            }
            ReadMembershipDataStore();
            MembershipUser user;
            if (users.TryGetValue(username, out user))
            {
                return user;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="totalRecords"></param>
        /// <returns></returns>
        public override MembershipUserCollection GetAllUsers(int pageIndex,
            int pageSize, out int totalRecords)
        {
            ReadMembershipDataStore();
            MembershipUserCollection users = new MembershipUserCollection();
            foreach (KeyValuePair<string, MembershipUser> pair in users)
            {
                users.Add(pair.Value);
            }
            totalRecords = users.Count;
            return users;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="email"></param>
        /// <param name="passwordQuestion"></param>
        /// <param name="passwordAnswer"></param>
        /// <param name="isApproved"></param>
        /// <param name="providerUserKey"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public override MembershipUser CreateUser(string username, string password, string email, 
            string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, 
            out MembershipCreateStatus status)
        {
            MembershipUser newUser = new MembershipUser(Name, username, providerUserKey, email, 
                passwordQuestion, "", isApproved, false, DateTime.Now, DateTime.Now, DateTime.Now, 
                DateTime.Now, DateTime.Now);
            status = MembershipCreateStatus.ProviderError;
            SaltedHash saltedHash = new SaltedHash().Create(password);
            XElement root = XElement.Load(this.xmlFileName);
            var query = from user in root.Descendants("User")
                        let UserName = (string)root.Element("UserName")
                        let Email = (string)root.Element("Email")
                        select new { UserName, Email };
            XElement users = XElement.Load(this.xmlFileName, LoadOptions.None);
            XElement userElement = new XElement("User");
            userElement.Add(new XElement("ProviderUserKey", providerUserKey.ToString()));
            userElement.Add(new XElement("UserName", username));
            userElement.Add(new XElement("PasswordSalt", saltedHash.Salt));
            userElement.Add(new XElement("PasswordHash", saltedHash.Hash));
            userElement.Add(new XElement("Email", email));
            userElement.Add(new XElement("PasswordQuestion", passwordQuestion));
            userElement.Add(new XElement("PasswordAnswer", passwordAnswer));
            userElement.Add(new XElement("IsApproved", isApproved.ToString()));
            userElement.Add(new XElement("IsLockedOut", "false"));
            userElement.Add(new XElement("CreationDate", DateTime.Now));
            userElement.Add(new XElement("LastActivityDate", Convert.ToDateTime("1/1/1753 12:00:00 AM")));
            userElement.Add(new XElement("LastLoginDate", Convert.ToDateTime("1/1/1753 12:00:00 AM")));
            userElement.Add(new XElement("LastLockoutDate", Convert.ToDateTime("1/1/1753 12:00:00 AM")));
            userElement.Add(new XElement("LastPasswordChangedDate", Convert.ToDateTime("1/1/1753 12:00:00 AM")));
            users.Add(userElement);
            try
            {
                users.Save(this.xmlFileName, SaveOptions.None);
                status = MembershipCreateStatus.Success;
            }
            catch (Exception ex)
            {

            }
            finally 
            { 

            }
            return newUser;
        }
                
        /// <summary>
        /// 
        /// </summary>
        private void ReadMembershipDataStore()
        {
            lock (this)
            {
                if (users == null)
                {
                    users = new Dictionary<string, MembershipUser>
                       (16, StringComparer.InvariantCultureIgnoreCase);

                    XElement root = XElement.Load(this.xmlFileName);
                    var query = from user in root.Descendants("User")
                                let ProviderUserKey = (string)user.Element("ProviderUserKey")
                                let UserName = (string)user.Element("UserName")
                                let Password = (string)user.Element("Password")
                                let Email = (string)user.Element("Email")
                                let PasswordQuestion = (string)user.Element("PasswordQuestion")
                                let PasswordAnswer = (string)user.Element("PasswordAnswer")
                                let IsApproved = (string)user.Element("IsApproved")
                                let IsLockedOut = (string)user.Element("IsLockedOut")
                                let CreationDate = (string)user.Element("CreationDate")
                                let LastLoginDate = (string)user.Element("LastLoginDate")
                                let LastActivityDate = (string)user.Element("LastActivityDate")
                                let LastPasswordChangedDate = (string)user.Element("LastPasswordChangedDate")
                                let LastLockOutDate = (string)user.Element("LastLockOutDate")
                                orderby UserName
                                select new
                                {
                                    ProviderUserKey,
                                    UserName,
                                    Password,
                                    Email,
                                    PasswordQuestion,
                                    PasswordAnswer,
                                    IsApproved,
                                    IsLockedOut,
                                    CreationDate,
                                    LastLoginDate,
                                    LastActivityDate,
                                    LastPasswordChangedDate,
                                    LastLockOutDate
                                };
                    foreach (var user in query)
                    {
                        MembershipUser newUser = new MembershipUser(
                            Name, user.UserName, user.ProviderUserKey, user.Email, user.PasswordQuestion,
                            " ", Convert.ToBoolean(user.IsApproved),  Convert.ToBoolean(user.IsLockedOut), 
                            Convert.ToDateTime(user.CreationDate),
                            Convert.ToDateTime(user.LastLoginDate), Convert.ToDateTime(user.LastActivityDate), 
                            Convert.ToDateTime(user.LastPasswordChangedDate), Convert.ToDateTime(user.LastLockOutDate)
                        );

                        users.Add(user.UserName, newUser);
                    }
                }
            }

        }

        #region Not Supported
        public override int GetNumberOfUsersOnline()
        {
            throw new NotSupportedException();
        }

        public override bool ChangePassword(string username,
            string oldPassword, string newPassword)
        {
            throw new NotSupportedException();
        }

        public override bool
            ChangePasswordQuestionAndAnswer(string username,
            string password, string newPasswordQuestion,
            string newPasswordAnswer)
        {
            throw new NotSupportedException();
        }

        public override bool DeleteUser(string username,
            bool deleteAllRelatedData)
        {
            throw new NotSupportedException();
        }

        public override MembershipUserCollection
            FindUsersByEmail(string emailToMatch, int pageIndex,
            int pageSize, out int totalRecords)
        {
            throw new NotSupportedException();
        }

        public override MembershipUserCollection
            FindUsersByName(string usernameToMatch, int pageIndex,
            int pageSize, out int totalRecords)
        {
            throw new NotSupportedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotSupportedException();
        }

        public override MembershipUser GetUser(object providerUserKey,
            bool userIsOnline)
        {
            throw new NotSupportedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            throw new NotSupportedException();
        }

        public override string ResetPassword(string username,
            string answer)
        {
            throw new NotSupportedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotSupportedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotSupportedException();
        }

        #endregion

        #endregion
    }
}
